﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using Xceed.Silverlight.DataGrid.Utils;
using Xceed.Silverlight.DragDrop;

namespace Xceed.Silverlight.DataGrid
{
  #region Metadata

  [TemplatePart( Name = "DragContentHost", Type = typeof( FrameworkElement ) )]

  [TemplateVisualState( GroupName = "CommonStates", Name = "Normal" )]
  [TemplateVisualState( GroupName = "CommonStates", Name = "MouseOver" )]
  [TemplateVisualState( GroupName = "CommonStates", Name = "Dragged" )]

  [TemplateVisualState( GroupName = "CurrentStates", Name = "Current" )]
  [TemplateVisualState( GroupName = "CurrentStates", Name = "NotCurrent" )]

  [System.Diagnostics.DebuggerDisplay( "Column = {DebuggerDisplayFieldName}, Content = {Content}" )]

  #endregion

  public class Cell : ContentControlBase, IColumnElement, IProvideDraggedContentPresenter, INotifyPropertyChanged
  {
    #region Static Fields

    internal static readonly XceedRoutedEvent OnMouseEnterCellEvent = new XceedRoutedEvent();
    internal static readonly XceedRoutedEvent OnMouseLeaveCellEvent = new XceedRoutedEvent();
    internal static readonly XceedRoutedEvent CellMouseLeftButtonDownEvent = new XceedRoutedEvent();
    internal static readonly XceedRoutedEvent CellMouseLeftButtonUpEvent = new XceedRoutedEvent();


    internal static readonly string CommonStateNormal = "Normal";
    internal static readonly string CommonStatePressed = "Pressed";
    internal static readonly string CommonStateDragged = "Dragged";
    internal static readonly string CommonStateMouseOver = "MouseOver";
    internal static readonly string CurrentStateCurrent = "Current";
    internal static readonly string CurrentStateNotCurrent = "NotCurrent";

    #endregion

    #region Constructor

    public Cell()
    {
      this.DefaultStyleKey = typeof( Cell );
      this.VisualStateHelper = new VisualStateHelper( this );

      m_propertyChangedHelper = new NotifyPropertyChangedHelper( this, this.RaisePropertyChangedEvent );

      m_dragContextPresenterFadeOut = new Storyboard();
      DoubleAnimation fadeOutAnimation = new DoubleAnimation();
      fadeOutAnimation.To = 0d;
      fadeOutAnimation.Duration = TimeSpan.FromMilliseconds( 2250 );
      fadeOutAnimation.FillBehavior = FillBehavior.HoldEnd;
      Storyboard.SetTargetProperty( fadeOutAnimation, new PropertyPath( FrameworkElement.OpacityProperty ) );

      m_dragContextPresenterFadeOut.Children.Add( fadeOutAnimation );

      m_dragContextPresenterFadeIn = new Storyboard();
      DoubleAnimation fadeInAnimation = new DoubleAnimation();
      fadeInAnimation.To = 1d;
      fadeInAnimation.Duration = TimeSpan.FromMilliseconds( 2250 );
      fadeInAnimation.FillBehavior = FillBehavior.HoldEnd;
      Storyboard.SetTargetProperty( fadeInAnimation, new PropertyPath( FrameworkElement.OpacityProperty ) );

      m_dragContextPresenterFadeIn.Children.Add( fadeInAnimation );
    }

    #endregion

    #region DraggedContentImageSource Property (Internal)

    internal static readonly DependencyProperty DraggedContentImageSourceProperty = DependencyProperty.RegisterAttached(
      "DraggedContentImageSource",
      typeof( ImageSource ),
      typeof( Cell ),
      new PropertyMetadata( null ) );

    // This must public to avoid a xaml parse exception while loading the 
    // DataTemplate for the drag content presenter.
    public static ImageSource GetDraggedContentImageSource( DependencyObject obj )
    {
      return ( ImageSource )obj.GetValue( Cell.DraggedContentImageSourceProperty );
    }

    internal static void SetDraggedContentImageSource( DependencyObject obj, ImageSource value )
    {
      obj.SetValue( Cell.DraggedContentImageSourceProperty, value );
    }

    #endregion // DraggedContentImageSource Property

    #region DragContentPresenter Property (Internal)

    internal ContentPresenter DragContentPresenter
    {
      get
      {
        return m_dragContentPresenter;
      }
    }

    #endregion // DragContentPresenter Property (Internal)

    #region FollowsParentColumnDrag Property

    public virtual bool FollowsParentColumnDrag
    {
      get
      {
        return true;
      }
    }

    #endregion

    #region ParentRow Property

    public Row ParentRow
    {
      get
      {
        Row row = this.ParentHost as Row;

        if( row == null )
          throw new DataGridInternalException();

        return row;
      }
    }

    #endregion ParentRow Property

    #region ParentColumn Property

    public Column ParentColumn
    {
      get
      {
        return ( this.ParentColumnContainer != null )
          ? this.ParentColumnContainer.Column
          : null;
      }
    }

    #endregion ParentColumn Property

    #region ParentColumnContainer Property

    internal ColumnContainer ParentColumnContainer
    {
      get;
      private set;
    }

    #endregion ParentColumnContainer Property

    #region ParentHost Property

    internal IColumnElementHost ParentHost
    {
      get;
      private set;
    }

    #endregion ParentHost Property

    #region VisualState Property

    internal VisualStateHelper VisualStateHelper
    {
      get;
      private set;
    }

    #endregion

    #region DebuggerDisplayFieldName Internal Property

    internal string DebuggerDisplayFieldName
    {
      get
      {
        var column = this.ParentColumn;
        if( column == null )
          return "{unbound}";

        return column.FieldName;
      }
    }

    #endregion


    public override void OnApplyTemplate()
    {
      base.OnApplyTemplate();

      this.VisualStateHelper.ApplyVisualStates();
    }

    protected override void OnMouseEnter( MouseEventArgs e )
    {
      base.OnMouseEnter( e );

      this.EventManager.RaiseEvent( Cell.OnMouseEnterCellEvent, new XceedRoutedEventArgs( this ) );
    }

    protected override void OnMouseLeave( MouseEventArgs e )
    {
      base.OnMouseLeave( e );

      this.EventManager.RaiseEvent( Cell.OnMouseLeaveCellEvent, new XceedRoutedEventArgs( this ) );
    }

    protected override void OnMouseLeftButtonDown( MouseButtonEventArgs e )
    {
      XceedEventUtils.RaiseMouseButtonEvent( this, e, Cell.CellMouseLeftButtonDownEvent );
    }

    protected override void OnMouseLeftButtonUp( MouseButtonEventArgs e )
    {
      XceedEventUtils.RaiseMouseButtonEvent( this, e, Cell.CellMouseLeftButtonUpEvent );
    }

    internal virtual void OnColumnIsDragPausedChanged()
    {
      if( m_dragContentPresenter != null )
      {
        if( this.ParentColumnContainer.IsDragPaused )
        {
          m_dragContextPresenterFadeOut.Begin();
          m_dragContextPresenterFadeIn.Stop();
        }
        else
        {
          m_dragContextPresenterFadeIn.Begin();
          m_dragContextPresenterFadeOut.Stop();
        }
      }
    }

    internal void SetCommonState( string state, bool useTransitions )
    {
      this.VisualStateHelper.GoToCommonState( state, useTransitions );
    }

    internal void SetCurrentState( bool isCurrent, bool useTransitions )
    {
      string state = ( isCurrent ) ? Cell.CurrentStateCurrent : Cell.CurrentStateNotCurrent;
      this.VisualStateHelper.GoToCurrentState( state, useTransitions );
    }

    internal void SetHorizontalContentAlignmentProperty( Nullable<HorizontalAlignment> newValue )
    {
      this.SetContentAlignment( Control.HorizontalContentAlignmentProperty, newValue );
    }

    internal void SetVerticalContentAlignmentProperty( Nullable<VerticalAlignment> newValue )
    {
      this.SetContentAlignment( Control.VerticalContentAlignmentProperty, newValue );
    }

    internal virtual void SetFixedColumnToggleButtonVisibleState( string state, bool useTransitions )
    {
    }

    internal void SetCellStyle( Style style )
    {
      if( style != this.Style )
      {
        if( style == null )
        {
          this.ClearValue( Cell.StyleProperty );
        }
        else
        {
          this.Style = style;
        }
      }
    }

    private void SetContentAlignment<TAlignment>( DependencyProperty dp, Nullable<TAlignment> newValue ) where TAlignment : struct
    {
      var oldValue = this.ReadLocalValue( dp );
      var hasOldValue = ( oldValue != DependencyProperty.UnsetValue );

      if( newValue.HasValue )
      {
        var value = newValue.Value;

        if( !hasOldValue || ( !object.Equals( oldValue, value ) ) )
        {
          this.SetValue( dp, value );
        }
      }
      else if( hasOldValue )
      {
        this.ClearValue( dp );
      }
    }

    #region IColumnElement Interface

    ColumnContainer IColumnElement.ParentColumnContainer
    {
      get
      {
        return this.ParentColumnContainer;
      }
    }

    IColumnElementHost IColumnElement.ParentHost
    {
      get
      {
        return this.ParentHost;
      }
    }

    void IColumnElement.SetParentColumnContainer( ColumnContainer columnContainer )
    {
      this.ParentColumnContainer = columnContainer;
    }

    void IColumnElement.SetParentHost( IColumnElementHost parentHost )
    {
      this.ParentHost = parentHost;
    }

    #endregion IColumnElement Interface

    #region IProvideDraggedContentPresenter Members

    public ContentPresenter CreateDraggedContentPresenter()
    {
      return new ContentPresenter();
    }

    public void PrepareDraggedContentPresenter( ContentPresenter contentPresenter )
    {
      m_dragContentPresenter = contentPresenter;

      Storyboard.SetTarget( m_dragContextPresenterFadeOut.Children[ 0 ], contentPresenter );
      Storyboard.SetTarget( m_dragContextPresenterFadeIn.Children[ 0 ], contentPresenter );

      if( m_dragContentPresenter != null )
      {
        FrameworkElement dragContentHost = this.GetTemplateChild( "DragContentHost" ) as FrameworkElement;

        Cell.SetDraggedContentImageSource(
          m_dragContentPresenter,
          Xceed.Silverlight.Compatibility.CompatibilityUtils.CreateImageFrom( ( dragContentHost == null ) ? this : dragContentHost ) );
      }
    }

    public void ClearDraggedContentPresenter( ContentPresenter contentPresenter )
    {
      m_dragContentPresenter = null;

      m_dragContextPresenterFadeOut.Stop();
      m_dragContextPresenterFadeIn.Stop();
    }

    #endregion

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    protected void RaisePropertyChanged( string propertyName )
    {
      m_propertyChangedHelper.RaisePropertyChanged( propertyName );
    }

    protected void RaisePropertyChanged<TMember>( Expression<Func<TMember>> expression )
    {
      m_propertyChangedHelper.RaisePropertyChanged( expression );
    }

    private void RaisePropertyChangedEvent( string propertyName )
    {
      if( this.PropertyChanged != null )
      {
        this.PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) );
      }
    }

    private readonly NotifyPropertyChangedHelper m_propertyChangedHelper;

    #endregion

    #region Private Fields

    private ContentPresenter m_dragContentPresenter;
    private Storyboard m_dragContextPresenterFadeOut;
    private Storyboard m_dragContextPresenterFadeIn;

    #endregion
  }
}
